package com.changgou.auth.service.impl;

import com.changgou.auth.service.AuthService;
import com.changgou.auth.util.AuthToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class AuthServiceImpl implements AuthService {

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Value("${auth.ttl}")
    private Long ttl;

    @Override
    public AuthToken login(String username, String password, String clientId, String clientSecret) {

        //1. 获取用户认证服务的实例对象
        ServiceInstance serviceInstance = loadBalancerClient.choose("USER-AUTH");

        //2. 拼接申请令牌的请求路径
        String url = serviceInstance.getUri() + "/oauth/token";
        System.out.println("url : " + url);
        //3. 发送请求
        //3.1 封装请求参数
        MultiValueMap<String, Object> params = new LinkedMultiValueMap<String, Object>();
        params.add("grant_type", "password");
        params.add("username", username);
        params.add("password", password);

        //3.2 封装请求头
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>();
        headers.add("Authorization", this.getHttpBasic(clientId, clientSecret));

        //可以在发送请求前指定异常处理方式
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
                if (statusCode.value() != 400 && statusCode.value() != 401) {
                    super.handleError(response, statusCode);
                }
            }
        });

        //3.3 发送请求
        ResponseEntity<Map> exchange = restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<MultiValueMap<String, Object>>(params, headers), Map.class);

        //4. 获取响应结果
        Map<String, Object> responseBody = exchange.getBody();

        for (Map.Entry<String, Object> entry : responseBody.entrySet()) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }

        //封装响应的token数据
        if (responseBody==null || responseBody.get("access_token")==null || responseBody.get("refresh_token")==null || responseBody.get("jti")==null){
            throw new RuntimeException("申请令牌失败");
        }

        AuthToken authToken = new AuthToken();
        authToken.setAccessToken(responseBody.get("access_token").toString());
        authToken.setRefreshToken(responseBody.get("refresh_token").toString());
        authToken.setJti(responseBody.get("jti").toString());

        //存储令牌数据到redis  以令牌短标识jti作为key 令牌字符串作为value
        redisTemplate.boundValueOps(authToken.getJti()).set(authToken.getAccessToken(),ttl, TimeUnit.SECONDS);

        return authToken;
    }

    /**
     * 进行http basic认证操作，把客户端id和客户端密码进行拼接，然后用base64进行编码
     *
     * @param username
     * @param password
     * @return
     */
    private String getHttpBasic(String username, String password) {
        String baseStr = username + ":" + password;
        byte[] encode = Base64Utils.encode(baseStr.getBytes());
        return "Basic " + new String(encode);
    }

}
